package dryven.codegen.asm;

import java.lang.reflect.Method;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import dryven.codegen.asm.typeinfo.ClassTypeInfo;
import dryven.codegen.asm.typeinfo.TypeInfo;

public abstract class AbstractInvokable implements Invokable, SubOperationListener {

	private MethodVisitor mv;
	private MethodBuilder mb;

	public AbstractInvokable(MethodBuilder mb) {
		super();
		this.mb = mb;
		mv = mb.getMethodVisitor();
	}

	protected MethodBuilder getBuilder() {
		return mb;
	}

	@Override
	public TempValue invoke(String method, TypeInfo returnType, ReadableSymbol... params) {
		checkIfInvokable();
		
		preOperation(0);
		
		StringBuilder signature = new StringBuilder();
		signature.append('(');
		for (ReadableSymbol readable : params) {
			readable.read();
			signature.append(readable.getTypeInfo().getTypeDescriptor());
		}
		signature.append(')');
		signature.append(returnType.getTypeDescriptor());
		int opcode = Opcodes.INVOKEVIRTUAL;
		if(method.equals("<init>")) {
			opcode = Opcodes.INVOKESPECIAL;	//TODO also check for private methods
		}
		else if(getTypeInfo().isInterface()) {
			opcode = Opcodes.INVOKEINTERFACE;
		}
		mv.visitMethodInsn(opcode, getTypeInfo().getInternalClassName(), method, signature.toString());
		
		for(int i=0;i<params.length;++i) {
			mb.popCurrentStackValue();
		}
		
		postOperation();
		
		
		if(returnType.isVoid()) {	//TODO check if void method calls really don't occupy a slot in the operand stack
			return null;
		} else {
			return new TempValue(returnType,mb);
		}
	}

	@Override
	public TempValue invoke(Method method, ReadableSymbol... params) {
		return invoke(method.getName(), new ClassTypeInfo(method.getReturnType()), params);
	}

	@Override
	public TempValue invoke(String method, TypeInfo returnType,
			TempValuePreparer tvp) {
		checkIfInvokable();

		preOperation(0);
		
		tvp.addValues();
		StringBuilder signature = new StringBuilder();
		signature.append('(');
		for (TempValue tv :tvp.getValues()) {
			signature.append(tv.getTypeInfo().getTypeDescriptor());
		}
		signature.append(')');
		signature.append(returnType.getTypeDescriptor());
		int opcode = Opcodes.INVOKEVIRTUAL;
		if(method.equals("<init>")) {
			opcode = Opcodes.INVOKESPECIAL;
		}
		else if(getTypeInfo().isInterface()) {
			opcode = Opcodes.INVOKEINTERFACE;
		}
		mv.visitMethodInsn(opcode, getTypeInfo().getInternalClassName(), method, signature.toString());
		
		for(int i=0;i<tvp.getValues().size();++i) {
			mb.popCurrentStackValue();
		}
		
		postOperation();
		
		if(returnType.isVoid()) {	//TODO check if void method calls really don't occupy a slot in the operand stack
			return null;
		} else {
			return new TempValue(returnType,mb);
		}
	}
	
	protected void checkIfInvokable() {
		if(!getTypeInfo().isReference()) {
			throw new RuntimeException("Cannot invoke methods on primitive types");
		}
	}

	@Override
	public TempValue invoke(Method method, TempValuePreparer tvp) {
		return invoke(method.getName(), new ClassTypeInfo(method.getReturnType()), tvp);
	}

	@Override
	public TempValue invoke(String method, Class<?> returnType,
			ReadableSymbol... params) {
		return invoke(method, new ClassTypeInfo(returnType), params);
	}

	@Override
	public TempValue invoke(String method, Class<?> returnType,
			TempValuePreparer tvp) {
		return invoke(method, new ClassTypeInfo(returnType), tvp);
	}
}
